using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using System.Xml.Serialization;
using Tomahawk.Runtime.Serialization;

namespace Tomahawk.Runtime.Logic
{

    /// <summary>
    /// Actors are objects that can be placed in a virtual 3D world. 
    /// You can add feature to actors be derivating from it, or by attaching reusable components.
    /// </summary>
    public abstract class XActor : XObject
    {
        /// <summary>
        /// Construction
        /// </summary>
        protected XActor()
        {
            components = new SerializableList<XComponent>();
        }

        #region "Components handling"

        /// <summary>
        /// List of components
        /// </summary>
        private SerializableList<XComponent> components;

        /// <summary>
        /// List of components
        /// </summary>
        [XmlElement("Components")]
        public SerializableList<XComponent> Components
        {
            get { return components; }
            set { components = value; }
        }

        /// <summary>
        /// Add a new component to this actor
        /// </summary>
        public void AddComponent(XComponent component)
        {
            //set parental relation
            component.SetOwner(this);
            
            //if it's an scene componente, make it a child of the actor's one
            XSceneComponent sceneCmp = component as XSceneComponent;
            if ((sceneCmp != null) && (sceneCmp != this.SceneComponent))
            {
                sceneCmp.SetParent(this.SceneComponent);
            }

            //add to components array
            components.Add(component);
        }

        /// <summary>
        /// Remove a component from this actor
        /// </summary>
        public void RemoveComponent(XComponent component)
        {
            component.SetOwner(null);
            components.Remove(component);
        }

        /// <summary>
        /// Gets the first component of the specified type
        /// </summary>
        public T GetComponent<T>() where T: XComponent
        {
            foreach (XComponent cmp in this.components)
            {
                if (cmp is T)
                {
                    return cmp as T;
                }
            }

            return null;
        }

        /// <summary>
        /// Finds the first component of specified type that is registered in this actor.
        /// </summary>
        protected XComponent GetComponentOfType(Type cmpType)
        {
            XComponent retVal = null;
            foreach (XComponent cmp in this.components)
            {
                if (cmpType.IsInstanceOfType(cmp))
                {
                    retVal = cmp;
                    break;
                }
            }
            return retVal;
        }

        #endregion

        #region "World positioning"

        /// <summary>
        /// Each actor will have a default scenenode to
        /// handle all positioning and parental information
        /// </summary>
        public XSceneComponent SceneComponent;

        /// <summary>
        /// Initial location of the actor. NOTE: Use SetLocation to update position
        /// </summary>
        [XmlElement("Location")]
        public Vector3 DefaultLocation = Vector3.Zero;

        /// <summary>
        /// Initial scale of the actor. NOTE: Use SetScale to update scale
        /// </summary>
        [XmlElement("Scale")]
        public Vector3 DefaultScale = Vector3.One;

        /// <summary>
        /// Initial rotation of the actor. NOTE: Use SetRotation to update the rotation
        /// </summary>
        [XmlElement("Rotation")]
        public Quaternion DefaultRotation = Quaternion.Identity;

        /// <summary>
        /// Sets a new rotation
        /// </summary>
        public void SetRotation(ref Quaternion rotation)
        {
            this.SceneComponent.SceneNode.Rotation.X = rotation.X;
            this.SceneComponent.SceneNode.Rotation.Y = rotation.Y;
            this.SceneComponent.SceneNode.Rotation.Z = rotation.Z;
            this.SceneComponent.SceneNode.Rotation.W = rotation.W;
        }

        /// <summary>
        /// Sets a new rotation
        /// </summary>
        public void SetRotation(Quaternion rotation)
        {
            this.SceneComponent.SceneNode.Rotation = rotation;
        }

        /// <summary>
        /// Sets a new position
        /// </summary>
        public void SetLocation(ref Vector3 location)
        {
            this.SceneComponent.SceneNode.Position.X = location.X;
            this.SceneComponent.SceneNode.Position.Y = location.Y;
            this.SceneComponent.SceneNode.Position.Z = location.Z;
        }

        /// <summary>
        /// Sets a new position
        /// </summary>
        public void SetLocation(Vector3 location)
        {
            this.SceneComponent.SceneNode.Position = location;
        }

        /// <summary>
        /// Sets a new scale
        /// </summary>
        public void SetScale(ref Vector3 scale)
        {
            this.SceneComponent.SceneNode.Scale.X = scale.X;
            this.SceneComponent.SceneNode.Scale.Y = scale.Y;
            this.SceneComponent.SceneNode.Scale.Z = scale.Z;
        }

        /// <summary>
        /// Sets a new scale
        /// </summary>
        public void SetScale(Vector3 scale)
        {
            this.SceneComponent.SceneNode.Scale = scale;
        }

        #endregion

        #region "Initialization"

        /// <summary>
        /// Generic initialization of an Actor
        /// </summary>
        protected override void OnInitialize()
        {
            base.OnInitialize();

            //Set as owner of the existing componentes (those which came from Xml deserialization)
            foreach (XComponent cmp in this.components)
            {
                //register the component in the world
                XWorld.Instance.registerObject(this, "", cmp);
                cmp.SetOwner(this);
            }

            //create the scene component that will be used as a base 
            SceneComponent = (XSceneComponent)XWorld.Instance.CreateObject(typeof(XSceneComponent));
            this.AddComponent(SceneComponent);

            //Set default positioning 0
            this.SceneComponent.SceneNode.Position = this.DefaultLocation;
            this.SceneComponent.SceneNode.Rotation = this.DefaultRotation;
            this.SceneComponent.SceneNode.Scale = this.DefaultScale;

            //Take each of the scene components and assign the base one as its parent
            foreach (XComponent cmp in this.components)
            {
                if (cmp != SceneComponent)
                {
                    XSceneComponent sceneCmp = cmp as XSceneComponent;
                    if (sceneCmp != null) sceneCmp.SetParent(this.SceneComponent);
                }
            }

        }

        #endregion

        #region "Destruction"

        /// <summary>
        /// Actor destruction
        /// </summary>
        public override void OnBeforeDestroy()
        {
            base.OnBeforeDestroy();

            //destroy all the components
            foreach (XComponent cmp in this.components)
            {
                cmp.Destroy();
            }
            this.components.Clear();
        }

        #endregion

        /// <summary>
        /// An actor will be ready when all of its components is ready
        /// </summary>
        /// <returns></returns>
        protected override bool checkResourcesReady()
        {
            bool retVal = false;

            //an actor will be ready when all its component are ready
            retVal = true;
            foreach (XComponent cmp in this.components)
            {
                retVal = retVal && cmp.IsReady;
            }

            return base.checkResourcesReady() && retVal;
        }

    }

}
